package com.notetool.datafetching;

import java.util.ArrayList;

import com.notetool.model.NoteSet;
import com.notetool.util.tokenparser.Token;
import com.notetool.util.tokenparser.TokenList;
import com.notetool.util.tokenparser.syntax.SingleCharModel;
import com.notetool.util.tokenparser.syntax.WordModel;

public class ComposedQueryNode extends QueryNode
{
    
    ////////////////////
    // Members
    private ArrayList<QueryNode> nodes;
    private ArrayList<NoteSetOperator> operators;

    ////////////////////
    // Constructors
    public ComposedQueryNode (Token first, Token last, IQueryMonitor monitor) throws QueryInitException, QueryStoppedException
    {
        nodes = new ArrayList<QueryNode> ();
        operators = new ArrayList<NoteSetOperator> ();
        
        buildNode (first, last, monitor);
    }
    
    ////////////////////
    // QueryNode implementation
    public NoteSet getNotes (IQueryMonitor monitor) throws QueryExecException, QueryStoppedException
    {
        QueryExecException ex = new QueryExecException ("ComposedQueryNode could not execute");
        
        // Empty case
        if (nodes.isEmpty ())
            throw ex;
        
        // Make sure we have enough operators
        if (operators.size () != nodes.size () - 1)
            throw ex;
        
        // Grab the value of the first noteSet
        NoteSet result = nodes.get (0).getNotes (monitor);
        
        // Apply operations
        for (int i = 1; i < nodes.size (); ++ i)
        {
            // Run checkpoint
            if (monitor != null)
                monitor.queryCheckpoint ();
            
            // Grab operator
            NoteSetOperator op = operators.get (i - 1);
            
            // Grab node's value
            NoteSet curSet = nodes.get (i).getNotes (monitor);
            
            // Apply operator
            op.applyOp (result, curSet);
        }
        
        return result;
    }
    
    ////////////////////
    // Private methods
    private void buildNode (Token first, Token last, IQueryMonitor monitor) throws QueryInitException, QueryStoppedException
    {
        QueryInitException ex = new QueryInitException ("ComposedQueryException could not be built.");
        
        if (first == null || last == null)
            throw ex;
        
        Token curTok = first;
        boolean nextId = false;
        boolean nextOp = false;
        boolean endReached = false;
        
        while (!endReached)
        {
            // Run checkpoint
            if (monitor != null)
                monitor.queryCheckpoint ();
            
            // End loop if end has been reached
            if (curTok == last)
                endReached = true;
            
            if (curTok == null)
                throw ex;
            
            // Attempt to parse operation if we're expecting an operation
            if (nextOp)
            {
                if (!curTok.is ("operator"))
                    throw ex;
                    
                if (curTok.is ("operatorUnion"))
                    operators.add (new UnionOperator ());
                
                if (curTok.is ("operatorIntersect"))
                    operators.add (new IntersectOperator ());
                
                nextOp = false;
                continue;
            }
            
            // If we're not parsing an operation, then on our next pass we'll be expecting one
            nextOp = true;
            
            // Handle sub node
            if (curTok.is ("groupingOpening"))
            {
                Token grpEnd = findGroupEnd (curTok);
                
                if (grpEnd == null)
                    throw ex;
                
                Token grpLast = grpEnd.getPrevious ();
                
                if (grpLast == null || grpLast == curTok)
                    throw ex;
                
                nodes.add (new ComposedQueryNode (curTok.getNext (), grpLast, monitor));
                
                continue;
            }
            
            // Handle id node indicator
            if (curTok.is ("id"))
            {
                nextId = true;
                continue;
            }
            
            // Handle id node
            if (nextId)
            {
                if (!curTok.is ("idWord"))
                    throw ex;
                
                nodes.add (new IdQueryNode (curTok.toString ()));
                
                nextId = false;
                
                continue;
            }
            
            // Handle tag node
            if (curTok.is ("tagWord"))
            {
                nodes.add (new TagQueryNode (curTok.toString ()));
                
                continue;
            }
            
        }
    }
    
    private Token findGroupEnd (Token token)
    {
        if (token == null)
            return null;
        
        int openings = 0;
        
        Token curTok = token;
        while (curTok != null)
        {
            if (curTok.is("groupingClosing"))
            {
                if (openings > 0)
                    openings -= 1;
                else
                    return curTok;
            }
                
            if (curTok.is("groupingOpening"))
                openings += 1;
            
            curTok = curTok.getNext ();
        }
        
        return null;
    }
    
    ////////////////////
    // Inner classes
    private abstract class NoteSetOperator
    {
        
        ////////////////////
        // Abstract methods
        public abstract NoteSet applyOp (NoteSet set1, NoteSet set2);
        
    }
    
    private class IntersectOperator extends NoteSetOperator
    {
        
        ////////////////////
        // NoteSetOperator implementation
        public NoteSet applyOp (NoteSet set1, NoteSet set2)
        {
            set1.intersect (set2);
            
            return set1; 
        }
        
    }
    
    private class UnionOperator extends NoteSetOperator
    {
        
        ////////////////////
        // NoteSetOperator implementation
        public NoteSet applyOp (NoteSet set1, NoteSet set2)
        {
            set1.union (set2);
            
            return set1;
        }
        
    }
    
}